<?php

/**
 * \file
 * Classe `join`.
 */
 
namespace pilar\ObjectCollections /* \cond */ ; /* \endcond */
{

/**
 * Jointure dans une sélection de collection d'objets d'entités en
 * base de données. Il s'agit aussi d'un membre de type "référence".
 *
 * \since 08/10/2008
 * \author Matthieu OVIEDO <matthieu.oviedo@gmail.com>
 */
class Join extends Member {

/**
 * Collection d'objets parente.
 *
 * \since 08/10/2008
 */
private $_parentCollection;


/**
 * Entité jointe.
 *
 * \since 08/10/2008
 */
public $_joinedEntity;


/**
 * Sous-jointures.
 *
 * \since 08/10/2008
 */
private $_subJoins = array();


/**
 * Membres chargés.
 *
 * \since 08/10/2008
 */
private $_members = array();


/**
 * Alias SQL de jointure.
 *
 * \since 08/10/2008
 */
private $_alias;


/**
 * Objet courant de la jointure.
 *
 * \since 08/10/2008
 */
private $_currentObject = null;


/**
 * Jointure exportée ?
 *
 * \since 08/10/2008
 */
private $_exported = false;


/**
 * Constructeur.
 * \since 08/10/2008
 * \param $parentCollection Collection d'objets parente.
 * \param $joinedEntity Entité jointe.
 * \param $alias Alias SQL associé à la jointure.
 * \param $parentJoin Jointure parente.
 * \param $refMember Membre de la référence de jointure.
 */
public function __construct($parentCollection,
                            $joinedEntity,
                            $alias,
                            $parentJoin,
                            $refMember) {
 // Appel du constructeur du membre :
  parent::__construct(
   $parentJoin,
   $refMember
  );

 $this->_parentCollection = $parentCollection;
 $this->_joinedEntity = $joinedEntity;
 $this->_alias = $alias;
 
 // Si on n'est pas une jointure principale alors on s'inscrit comme
 // autre jointure dans la collection parente :
  if($this->_parentJoin != null) {
   $this->_parentCollection->addJoin($this);
  }
}


/**
 * Accède à un élément de la jointure par son nom.
 * 
 * Si le nom est vide alors l'élément correspondra au champ \c INT de la
 * référence de la jointure. Si le nom n'est pas vide alors l'élément
 * correspondra à un membre de la jointure.
 * 
 * \since 08/10/2008
 * \param $name Nom de l'élément auquel on veut accéder.
 * \return Élément correspondant au nom \a $name.
 */
public function __get($name = null) {
 // Cas où on souhaite accéder au champ principal de la référence :
  if($name == null) return parent::field();
 
 // Le membre est-il déjà chargé ?
  if(isset($this->_members[$name])) return $this->_members[$name];
  
 // Instanciation du membre d'entité :
  $entityMember = $this->_joinedEntity->member($name);
 
 // Si le membre est une référence on renvoie une jointure :
  if($entityMember instanceof \pilar\Entities\Ref) {
   // Instanciation de la nouvelle jointure :
    $join = new Join(
     $this->_parentCollection,
     $entityMember->get('entity'),
     $this->_parentCollection->bookJoinAlias(),
     $this,
     $entityMember
    );
   
   // Référencement de la nouvelle jointure :
    // En tant que sous-jointure :
     $this->_subJoins[$name] = $join;
    // En tant que membre :
     $this->_members[$name] = $join;
   
   return $join;
  }
  
 // A ce stade on a affaire à un membre lambda.
 
 // Instanciation de la colonne du champ en question :
  $member = new Member(
   $this,
   $entityMember
  );
  
 // Référencement du nouveau membre :
  $this->_members[$name] = $member;
   
 return $member;
}


/**
 * Permet de spécifier les valeurs a assigner aux objets lors de
 * l'opération de mise à jour de la collection parente.
 *
 * \remarks
 * La valeur de la référence de la jointure courante ne peut être
 * modifiée via cette méthode.
 *
 * \since 08/10/2008
 * \param $values Valeurs à assigner.
 */
public function set($values) {
 foreach($values as $key => $value) {
  $this->_get($key)->set($value);
 }
}


/**
 * Sélectionne les sous-jointures à exporter et exporte la jointure
 * courante.
 *
 * \todo
 * Rendre possible l'exportation de <tt>foo->toto</tt> sans exporter
 * <tt>foo</tt>.
 *
 * \since 08/10/2008
 * \param $refs Structure en arbre des sous-jointures à exporter.
 */
public function export($refs = null) {
 $this->_exported = true;

 if($refs != null) {
  foreach($refs as $key => $value) {
   if(!is_array($value)) {
    $this->__get($value)->export(); /* On exporte simplement
                                       la jointure. */
   } else {
    $this->__get($key)->export($value); // On exporte non seulement
                                        // la jointure mais également
					// certaines de ses
					// sous-jointures.
   }
  }
 }
}


/**
 * Détermine si la jointure est exportée dans la sélection de la
 * collection parente.
 *
 * \since 08/10/2008
 * \return \c TRUE si la jointure est exportée, \c FALSE sinon.
 */
public function isExported() {
 return $this->_exported;
}


/**
 * Détermine si la jointure est nécessaire dans la sélection de la
 * collection parente.
 *
 * \since 08/10/2008
 * \return \c TRUE si la jointure est nécessaire, \c FALSE sinon.
 */
public function needed() {
 return (   $this->_exported
         || !empty($this->_subJoins)
         || !empty($this->_members)
        );
}


/**
 * Accède aux membres chargés dans la jointure.
 *
 * \since 08/10/2008
 * \return Membres chargés dans la jointures sous la forme
 *         <tt>{ $memberName => $member }</tt>.
 */
public function members() {
 return $this->_members;
}


/**
 * Accède à l'alias de la jointure.
 *
 * \since 08/10/2008
 * \return Alias de la jointure.
 */
public function alias() {
 return $this->_alias;
}


/**
 * Génère le code SQL de définition de la jointure courante.
 *
 * Il s'agit du code en <tt>LEFT JOIN ... ON ... = ...</tt>.
 *
 * \since 02/10/2008
 * \return Code SQL de définition de la jointure courante.
 */
public function sqlJoinCode() {
 return 'LEFT JOIN `'.$this->_joinedEntity->sqlTableName().'`'
      . ' AS '.$this->_alias
      . ' ON '
      .    $this->__get('id')->sqlCode()
      . ' = '
      .    $this->sqlCode();

}


/**
 * Accède à l'objet courant associé à la jointure.
 *
 * \todo
 * Voir pourquoi il est nécessaire de recréer un objet à chaque fois.
 *
 * \todo
 * Voir si la partie évaluant les références n'est pas un cas
 * particulier d'un problème plus général.
 *
 * \since 08/10/2008
 * \return Object courant.
 */
public function currentObject() {
 // On part d'un objet vide :
  if ($this->_currentObject == null || 1==1) {
   // On force l'instanciation d'un nouvel objet :
    $this->_currentObject = $this->_joinedEntity->newObject();
  } else {
   // On préfaire partir du même objet que l'on remet à zéro
   // artificiellement :
    $this->_currentObject->reset();
  }
 
 // Initialisation de l'identifiant de l'objet à partir de celui
 // issu de la requête SQL :
  $this->_currentObject->setId(
   $this->_parentCollection->_result->record[
    $this->__get('id')->sqlIdx()
   ]
  );
 
 // Synchronisation des données de l'objet avec les données de la base
 // de données :
  foreach ($this->members() as $memberName => $member) {
   $objectMember = $this->_currentObject->member($memberName);
   // Synchronisation des champs :
    foreach ($member->fields() as $fieldName => $field) {
     $objectField = $objectMember->field($fieldName);
     $objectField->sync(
      $this->_parentCollection->_result->record[$field->sqlIdx()]
     );
    }
   // Cas où le membre est une référence exportée et que la
   // valeur de la référence n'est pas nulle :
    if ($member->entityMember() instanceof \pilar\Entities\Ref
        and $member->isExported()
        and $objectMember->get() != 0) {
     // Évaluation de la référence dans l'objet courant :
      $objectMember->setRefObject(
       $member->currentObject()
      );
    }
  }

 return $this->_currentObject;
}


} // Class.

} // Namespace.

?>
